package dip.daide.comm;

import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;

import com.danicsoft.daide.binary.ClientConnection;
import com.danicsoft.daide.token.InvalidTokenException;
import com.danicsoft.daide.token.TokenGroup;
import com.danicsoft.daide.token.TokenMap;
import com.danicsoft.daide.token.TokenParser;

/**
 * The <code>Connection</code> class is responsible for communicating with the
 * AIServer. This includes doing handshaking, parsing the C-S Protocol and
 * sending/receiving diplomatic messages to and from the remote server.
 * Communication with the Connection class is done via the string representation
 * of the language syntax. Conversion between the string mnemonics and the
 * 2-octet data representation is handled by the <code>Connection</code>
 * class.
 * 
 * @author <a href="mailto:heb@ludd.luth.se">Henrik Bylund</a>
 * @version 1.0
 */
public class Connection implements com.danicsoft.daide.token.DiplomaticMessageListener {

	private DiplomaticMessageListener myListener;

	private com.danicsoft.daide.binary.Connection myConn;

	private TokenMap myMap = new TokenMap();

	private TokenParser parser;

	/**
	 * Creates a new connection to a remote server.
	 * 
	 * @param ip
	 *        The TCP/IP address of the remote server
	 * @param port
	 *        The port on which the remote server is listening.
	 * @param listener
	 *        The listener to which to send the diplomatic messages.
	 * @throws IOException
	 *         if data could not be sent to the server (Possibly because there
	 *         is no answer at the specified address)
	 * @throws DisconnectedException
	 *         if disconnected during the handshake process.
	 */
	public Connection(InetAddress ip, int port, DiplomaticMessageListener listener) throws IOException, DisconnectedException {
		// TODO make a historian for this group.
		myListener = listener;
		myConn = new ClientConnection(ip, port);

		parser = new ParserAdapter();
		parser.addMessageListener(this);

		myConn.connect(parser);

	}

	/**
	 * Disconnects from the AI server. Sends a <code>Final Message</code> to
	 * the server, then sets the state to "disconnected".
	 * 
	 * @throws IOException
	 *         If an error occurs on the underlying layer
	 * @throws DisconnectedException
	 *         If we're already disconnected
	 */
	public void disconnect() throws IOException, DisconnectedException {

	}

	/**
	 * Sends a message to the AI server. The array of tokens supplied will be
	 * sent as a <code>Diplomatic Message</code> to the server.
	 * 
	 * @param message
	 *        The tokens to send to the server.
	 * @throws UnknownTokenException
	 *         if the message contains a token that is unknown to the local
	 *         parser. Note that if all tokens are deemed to be ok, no exception
	 *         will be thrown even if the server labels a token as unknown. In
	 *         this case, an <code>Error Message</code> will be received from
	 *         the server instead.
	 * @throws DisconnectedException
	 *         if we're already disconnected from the server when attempting to
	 *         send this message.
	 */
	public void send(String[] message) throws UnknownTokenException, DisconnectedException {
		try {
			TokenGroup group = parser.tokenizeStrings(message);
			try {
				parser.send(group);
			}
			catch (com.danicsoft.daide.binary.DisconnectedException e) {
				throw new DisconnectedException();
			}
		}
		catch (InvalidTokenException e) {

			throw new UnknownTokenException(e.getOffendingToken());
		}
	}

	public void diplomaticMessageReceived(TokenGroup tokenGroup) {
		List<String> message = new ArrayList<String>();
		addToList(tokenGroup, message);
		String[] strMessage = new String[message.size()];
		int i = 0;
		for (String str : message) {
			strMessage[i] = str;
			i++;
		}
		myListener.diplomaticMessageReceived(strMessage);

	}

	private void addToList(TokenGroup groupToAdd, List<String> stringList) {
		for (com.danicsoft.daide.token.Token tok : groupToAdd) {
			if (tok.isGroup()) {
				stringList.add("(");
				addToList((TokenGroup) tok, stringList);
				stringList.add(")");
			}
			else {
				stringList.add(tok.toString());
			}
		}
	}

	/**
	 * This class adapts TokenParser to suit the needs of Henrik's framework.  It sets the attributes of the Token class.
	 * @author Daniel Yule (daniel.yule@gmail.com)
	 * @version 1.0
	 *
	 */
	private class ParserAdapter extends TokenParser {

		/**
		 * Create a new ParserAdapter from the Connection we are using.
		 */
		public ParserAdapter() {
			super(myConn);
		}

		/**
		 * Set the representation message for the Token class.
		 * 
		 * @param RM
		 *        The reprentation message as an array of bytes.
		 * @see com.danicsoft.daide.token.TokenParser#setRM(byte[])
		 */
		@Override
		public void setRM(byte[] RM) {
			for (int i = 0; i < RM.length; i += 6) {
				byte[] bits = new byte[] { RM[i], RM[i + 1] };
				byte[] token = new byte[] { RM[i + 2], RM[i + 3], RM[i + 4] };
				Token.add(bits, new String(token));

			}
			super.setRM(RM);
		}

	}
}
